(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
前面我們提過,Context api雖然方便,但是每一次context更新時都會迫使「有使用useContext
取得該context」的元件更新。這在專案有規模時會造成很嚴重的效能問題。
學了這麼多效能處理的方法,那麼我們到底要怎麼處理useContext的效能問題呢?
這個作法是React官方最推薦的。
以我們的範例來說,本來我們把isOpen
和setIsOpen
都包進去同一個Context。並讓MenuItem和Menu個別引入。但其實MenuItem只需要setIsOpen
,所以我們應該要把isOpen
和setIsOpen
分成兩個Context,這樣當isOpen
被更新時,就不會強迫渲染MenuItem
了。
import React from "react";
export const OpenContext = React.createContext(true);
export const SetOpenContext = React.createContext( ()=>{})
import React, {useState, useRef, useCallback, useMemo} from 'react';
import MenuItem from '../component/MenuItem';
import Menu from '../component/Menu';
import { OpenContext,SetOpenContext } from '../context/ControlContext';
let menuItemWording=[
"Like的發問",
"Like的回答",
"Like的文章",
"Like的留言"
];
const MenuPage = () =>{
const [isOpen, setIsOpen] = useState(true);
const renderCounter = useRef(0);
renderCounter.current++;
const handleClick = useCallback(() => {
console.log("counter is " + renderCounter.current);
},[renderCounter]);
const menuItemArr = useMemo(()=> {
return menuItemWording.map((wording) => <MenuItem text={wording}/>)
},[]);
return (
<OpenContext.Provider value={isOpen}>
<SetOpenContext.Provider value={setIsOpen}>
<Menu title={"Andy Chang的like"}>
{menuItemArr}
</Menu>
</SetOpenContext.Provider>
</OpenContext.Provider>
);
}
export default MenuPage;
import React, {useContext} from 'react';
import { SetOpenContext } from '../context/ControlContext';
const menuItemStyle = {
marginBottom: "7px",
paddingLeft: "26px",
listStyle: "none"
};
function MenuItem(props){
const setIsOpen = useContext(SetOpenContext);
return <li
style={menuItemStyle}
onClick={()=>{setIsOpen(false)}}
>
{props.text}
</li>;
}
export default MenuItem;
memo
先前我們有提過,memo
會去比對元件的props是不是有被改變。所以如果我們把context抽出來變成一個純用來傳資料的父元件,再用他包住本來的子元件,用props把要用的contex資料傳進去,最後再用memo
包住子元件。
當context更新時,React就只會重複渲染那個空的父元件,子元件在綁定props的context沒變時就不會渲染。
import React, {memo, useContext} from 'react';
import { OpenContext } from '../context/ControlContext';
const menuItemStyle = {
marginBottom: "7px",
paddingLeft: "26px",
listStyle: "none"
};
const MenuItemElement=memo((props)=>{
return <li style={menuItemStyle}>{props.text}</li>;
});
function MenuItem(props){
const isOpenUtil = useContext(OpenContext);
const func = isOpenUtil.setIsOpenContext;
return <MenuItemElement text={props.text} setIsOpen={func}/>;
}
export default MenuItem;
但是這樣的缺點也很明顯,會多出很多無用的空元件。
useMemo
這個方法最直觀,就是用useMemo
包住每個元件的回傳值,只在第二個相依變數array監聽會需要用到的context資料。這樣元件本身雖然會被重新執行,但useMemo
回傳的會是上一次記憶的東西。
但也因為這樣,很多元件都要包useMemo,就等於你要讓React多記憶很多東西,所以也不被建議。
import React, {useMemo,useContext} from 'react';
import { OpenContext } from '../context/ControlContext';
const menuItemStyle = {
marginBottom: "7px",
paddingLeft: "26px",
listStyle: "none"
};
const MenuItemElement=(props)=>{
return <li style={menuItemStyle}>{props.text}</li>;
}
function MenuItem(props){
const isOpenUtil = useContext(OpenContext);
const setIsOpen = isOpenUtil.setIsOpenContext;
return useMemo(()=><li style={menuItemStyle} onClick={()=>{func(false)}}>{props.text}</li>,[setIsOpen]);
}
export default MenuItem;
Context API的本意並不是讓我們進行多層state
的管理,而是讓多個共用資料的元件能夠方便隨著資料的更動而被更新。也因為這樣,React才會讓使用useContext
的元件都重新渲染,第三方的狀態管理工具也並沒有隨著Context API的出現而消失。
Redux就是最通用的第三方狀態管理函式庫,我們會在之後的文章中介紹。